#include <pipe.h>
#include <errors.h>
#include <sem.h>
#include <i386.h>
#include <debug.h>

struct str_pipedev;

struct str_pipe {
	uint_8 pipe_free;
	uint_8 buffer[PIPE_LONG];
	uint_16 bytes; 
	sem_t sem;
	struct str_pipedev *w_pipedev;
	struct str_pipedev *r_pipedev;
};
 
typedef struct str_pipe pipe;

struct str_pipedev {
	chardev cd;
	pipe *ppipe;
	uint_16 next_byte; 
	sem_t sem;
} __attribute__((packed));

typedef struct str_pipedev pipedev;

static uint_16 free_pipes;
static pipedev w_pipesdev_avaibles[MAX_PIPES];
static pipedev r_pipesdev_avaibles[MAX_PIPES];
static pipe pipes_avaibles[MAX_PIPES];

sint_32 pipe_read(chardev* this, void* buf, uint_32 size) {

	pipedev *r_pipe, *w_pipe;
	pipe *ppipe;
	uint_8 *buffer;
	int i = 0;

	r_pipe = (pipedev *) this;
	if (!r_pipe) return 0;

	buffer = (uint_8 *) buf;
	if (!buffer) return 0;

	ppipe = r_pipe->ppipe;

	w_pipe = ppipe->w_pipedev;
	if (w_pipe->cd.refcount == 0) return 0;

	while ( size && (ppipe->w_pipedev->cd.refcount || ppipe->bytes) ) {
		if (ppipe->bytes > 0) {
			
			sem_wait(&ppipe->sem);
			
			buffer[i] = ppipe->buffer[r_pipe->next_byte]; 
			r_pipe->next_byte = (r_pipe->next_byte + 1) % PIPE_LONG;
			ppipe->bytes--;
			size--;
			i++;
			
			sem_signaln(&ppipe->sem);
			
			sem_signaln(&(w_pipe->sem));
		} else {
			sem_wait(&r_pipe->sem);		
		}
	}

	return i;
}

sint_32 pipe_write(chardev* this, const void* buf, uint_32 size) {

	pipedev *w_pipe, *r_pipe;
	pipe *ppipe;
	uint_8 *buffer;
	int i = 0;

	w_pipe = (pipedev *) this;
	if (!w_pipe) return 0;

	buffer = (uint_8 *) buf;
	if (!buffer) return 0;

	ppipe = w_pipe->ppipe;

	r_pipe = ppipe->r_pipedev;
	if (r_pipe->cd.refcount == 0) return 0;

	while (size && (ppipe->r_pipedev->cd.refcount || ppipe->bytes < PIPE_LONG)) {
		if (ppipe->bytes < PIPE_LONG) {
			
			sem_wait(&ppipe->sem);
			
			ppipe->buffer[w_pipe->next_byte] = buffer[i]; 
			w_pipe->next_byte = (w_pipe->next_byte + 1) % PIPE_LONG;
			ppipe->bytes++;
			size--;
			i++;
			
			sem_signaln(&ppipe->sem);
			
			sem_signaln(&(r_pipe->sem));
		} else {
			sem_wait(&w_pipe->sem);		
		}
	}

	return i;
}

uint_32 pipe_flush(chardev* this) {

	pipedev *t_pipe, *o_pipe;
	pipe *ppipe;
	int i;

	t_pipe = (pipedev *) this;
	ppipe = t_pipe->ppipe;

	if (t_pipe != ppipe->r_pipedev) 
		o_pipe = ppipe->r_pipedev;
	else 
		o_pipe = ppipe->w_pipedev;

	t_pipe->cd.refcount = 0;

	//depierto a todos los que estaban del otro lado
	if (o_pipe->cd.refcount == 0) {
		ppipe->pipe_free = 1; 
		free_pipes++;
	}
	else {
		for (i = 0; i < o_pipe->cd.refcount; i++) {
			sem_signaln(&(o_pipe->sem));
		}
	}

	return 0;
}

sint_32 pipe_open(chardev* pipes[2]) {

	pipedev *r_pipedev, *w_pipedev;
	pipe *ppipe;
	int i;

	if (free_pipes == 0) {
		return -ENOPIPE;
	} else {
		free_pipes--;
	}

	//busco un pipe libre para la punta de lectura
	for (i = 0; !pipes_avaibles[i].pipe_free; i++);

	ppipe = &pipes_avaibles[i];
	ppipe->pipe_free = 0;
	
	ppipe->sem = SEM_NEW(1);
	
	r_pipedev = ppipe->r_pipedev;
	w_pipedev = ppipe->w_pipedev;

	//pipe
	ppipe->bytes = 0;

	//pipedev de lectura
	r_pipedev->next_byte = 0;
	r_pipedev->sem = SEM_NEW(0);
	r_pipedev->cd.clase = CHARDEV;
	r_pipedev->cd.refcount = 1;
	r_pipedev->cd.flush = pipe_flush;
	r_pipedev->cd.read = pipe_read;
	r_pipedev->cd.write = NULL;
	r_pipedev->cd.seek = NULL;
		
	//pipedev de escritura
	w_pipedev->next_byte = 0;
	w_pipedev->sem = SEM_NEW(0);
	w_pipedev->cd.clase = CHARDEV;
	w_pipedev->cd.refcount = 1;
	w_pipedev->cd.flush = pipe_flush;
	w_pipedev->cd.read = NULL;
	w_pipedev->cd.write = pipe_write;
	w_pipedev->cd.seek = NULL;

	pipes[0] = (chardev *) r_pipedev;
	pipes[1] = (chardev *) w_pipedev;

	return 0;
}

void pipe_init(void) {

	int i;

	//Marco a todos los pipes como libres;
	for (i = 0; i < MAX_PIPES; i++) {
		pipes_avaibles[i].pipe_free = 1;
		pipes_avaibles[i].r_pipedev = &r_pipesdev_avaibles[i];
		pipes_avaibles[i].w_pipedev = &w_pipesdev_avaibles[i];
		r_pipesdev_avaibles[i].ppipe = &pipes_avaibles[i];
		w_pipesdev_avaibles[i].ppipe = &pipes_avaibles[i];
	}

	free_pipes = MAX_PIPES;
}


